/*
 * Copyright (c) 2006 Your Corporation. All Rights Reserved.
 */

/**
 * Copyright 2007 Jason Horman, Pete Aykroyd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.functor.expression;

import org.apache.commons.collections.map.LRUMap;
import org.util.reflection.Statement;

import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
import java.util.Map;

/**
 * Base class for expression compilers.
 */
public abstract class ExpressionCompiler<BT, BPT extends BT> {
	/** Cache of compiled expressions. */
	private Map<Expression,BT> cache = Collections.synchronizedMap(new LRUMap(5000));

	public BT start(Expression expression) {
		if (expression == null)
			return null;
		BT compiled = (BT) cache.get(expression);
		if (compiled == null) {
			compiled = compile(expression);
			cache.put(expression, compiled);
		}

		return compiled;
	}

	/** Start visiting the node reflectively. */
	public BT compile(Expression expression) {
		// create the reflective statement
		Statement statement = new Statement(this, "visit", new Object[]{expression});
		try {
			return (BT) statement.execute();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public BPT visit(And and) {
		List<BPT> compiledExpressions = new ArrayList<BPT>(and.getPredicates().size());
		for (PredicateExpression expression : and.getPredicates()) {
			// compile the expression in the and
			BPT compiled = (BPT) compile(expression);
			if (compiled != null) {
				// add the expression to the junction
				compiledExpressions.add(compiled);
			}
		}

		if (compiledExpressions.isEmpty())
			return null;
		else
			return createAnd(compiledExpressions);
	}

	/** Subclasses should return their own AND implementation. */
	protected abstract BPT createAnd(List<BPT> predicates);

	public BPT visit(Or or) {
		List<BPT> compiledExpressions = new ArrayList<BPT>(or.getPredicates().size());
		for (PredicateExpression expression : or.getPredicates()) {
			// compile the expression in the or
			BPT compiled = (BPT) compile(expression);
			if (compiled != null) {
				// add the expression to the junction
				compiledExpressions.add(compiled);
			}
		}

		if (compiledExpressions.isEmpty())
			return null;
		else
			return createOr(compiledExpressions);
	}

	/** Subclasses should return their own OR implementation. */
	protected abstract BPT createOr(List<BPT> predicates);

	public BPT visit(Not not) {
		// co mpile the expression in the or
		BPT compiled = (BPT) compile(not.getPredicate());
		if (compiled != null) {
			// add the not to the stack
			return createNot(compiled);
		} else {
			// add the null to the stack
			return null;
		}
	}

	/** Subclasses should return their own  implementation. */
	public abstract BPT createNot(BPT predicate);
}
